
///std::thread提供了两种创建方式

use std::thread;
use std::time::Duration;
use std::sync::{mpsc,Arc, Mutex, Condvar};


///第一种方式是通过spawn函数来创建
pub fn thread_demo1(){
    //创建线程, move关键字: 获取线程外部变量的所有权
    let x = 230;
    let t = thread::spawn(move || {
        println!("I am a new thread, x = {}", x);
    });
    //等待线程执行完
    t.join().unwrap();
}

///第二种方式创建线程，它比第一种方式稍微复杂一点，因为功能强大一点，可以在创建之前设置线程的名称和堆栈大小

pub fn thread_demo2(){
    let t1 = thread::Builder::new()
        .name("t1".to_string())
        .stack_size(4 * 1024 * 1024)//堆栈大小为4M
        .spawn(move || {
           println!("I am thread1.");
        });
    match t1 {
        Ok(h) => {
            match h.join(){
                Ok(())=>{
                    println!("线程执行完成");
                },
                Err(e) =>{
                    
                }
            }
        },
        Err(e) =>{
            panic!("{}", e);
        }
    }
}


/// 通道 ust的通道(channel)可以把一个线程的消息(数据)传递到另一个线程，从而让信息在不同的线程中流动，
/// 从而实现协作。详情请参见std::sync::mpsc。通道的两端分别是发送者(Sender)和接收者(Receiver)，
/// 发送者负责从一个线程发送消息，接收者则在另一个线程中接收该消息。

///消息类型必须实现marker trait Send (标识trait)
/// 如果一个类型是Send，则表明它可以在线程间安全的转移所有权(ownership)，当所有权从一个线程转移到另一个线程后，
/// 同一时间就只会存在一个线程能访问它，这样就避免了数据竞争，从而做到线程安全
/// 目前几乎所有的原始类型都是Send，例如例子中发送的i32。对于开发者而言，我们可能会更关心哪些是非Send，也就是实现了!Send，因为这会导致线程不安全
///
/// 对于不是Send的情况（!Send），大致分为两类：
// 原始指针，包括*mut T和*const T，因为不同线程通过指针都可以访问数据，从而可能引发线程安全问题。
// Rc和Weak也不是，因为引用计数会被共享，但是并没有做并发控制。

///如果自定义类的所有字段都是Send，那么这个自定义类也是Send。 反之，如果有一个字段是!Send，那么这个自定义类也是!Send。
/// 如果类的字段存在递归包含的情况，按照该原则以此类推来推论类是Send还是!Send。
///在为一个自定义类实现Send或者!Send时，必须确保符合它的约定。

pub fn channel_demo1(){
    //创建一个通道
    let (tx, rx): (mpsc::Sender<i32>, mpsc::Receiver<i32>) = mpsc::channel();

    //创建线程用于发送消息
    thread::spawn(move || {
        //发送一个消息
        tx.send(23).unwrap();
    });

    //在主线程中接手子线程发送的消息并输出
    println!("receive {}", rx.recv().unwrap());
}
///异步通道(Channel) 不管接收者是否正在接收消息，消息发送者在发送消息时都不会阻塞
// 1.通道是可以同时支持多个发送者的，通过clone的方式来实现。 这类似于Rc的共享机制。 其实从Channel所在的
// 库名std::sync::mpsc也可以知道这点。 因为mpsc就是多生产者单消费者(Multiple Producers Single Consumer)的简写。
// 可以有多个发送者,但只能有一个接收者，即支持的N:1模式。

// 2.异步通道具备消息缓存的功能，因为1和2是在没有接收之前就发了的，在此之后还能接收到这两个消息。

//3.消息发送和接收的顺序是一致的，满足先进先出原则。

pub fn channel_demo2(){
    //创建一个通道
    let (tx, rx): (mpsc::Sender<i32>, mpsc::Receiver<i32>) = mpsc::channel();

    //创建线程用于发消息
    for id in 0..2 {
        // 注意Sender是可以clone的，这样就可以支持多个发送者 可以有多个发送者,但只能有一个接收者，即支持的N:1模式。
        let thread_tx = tx.clone();
        thread::spawn(move || {
            //发送消息
            thread_tx.send(id + 1).unwrap();
            println!("send {}", id + 1);
        });
    }
    thread::sleep(Duration::from_millis(2000));
    println!("wake up");
    //在主线程中接收子线程发送的消息并输出 接收者的recv方法会阻塞当前线程
    for _ in 0..2 {
        println!("receive {}", rx.recv().unwrap());
    }
}

///  同步通道  同步通道在使用上同异步通道一样，接收端也是一样的，唯一的区别在于发送端
/// 同步通道是需要指定缓存的消息个数的，但需要注意的是，最小可以是0，表示没有缓存。
/// 发送者是会被阻塞的。当通道的缓存队列不能再缓存消息时，发送者发送消息时，就会被阻塞。
pub fn channel_demo3(){
    // 创建一个同步通道
    let (tx, rx): (mpsc::SyncSender<i32>, mpsc::Receiver<i32>) = mpsc::sync_channel(0);
    // 创建线程用于发送消息
    let new_thread = thread::spawn(move || {
        // 发送一个消息，此处是数字id
        println!("before send");
        tx.send(1).unwrap();
        println!("after send");
    });
    println!("before sleep");
    thread::sleep(Duration::from_millis(5000));
    println!("after sleep");
    // 在主线程中接收子线程发送的消息并输出
    println!("receive {}", rx.recv().unwrap());
    new_thread.join().unwrap();
}


///共享内存
///static
///Rust语言中也存在static变量，其生命周期是整个应用程序，并且在内存中某个固定地址处只存在一份实例。所有线程都能够访问到它。
///这种方式也是最简单和直接的共享方式。几乎大多数语言都存在这种机制。下面简单看一下Rust中多个线程访问static变量的用法：

static mut VAR: i32 = 5;

//从结果来看VAR的值变了，从代码上来看，除了在VAR变量前面加了mut关键字外，更加明显的是在使用VAR的地方都添加了unsafe代码块。
// 为什么？所有的线程都能访问VAR，且它是可以被修改的，自然就是不安全的。上面的代码比较简单，同一时间只会有一个线程读写VAR，
// 不会有什么问题，所以用unsafe来标记就可以。如果是更多的线程，还是请使用接下来要介绍的同步机制来处理。

//static如此，那const呢？ const会在编译时内联到代码中，所以不会存在某个固定的内存地址上，也不存在可以修改的情况，并不是内存共享的。
pub fn share_mem_demo1(){
    // 创建一个新线程
    let new_thread = thread::spawn(move|| {
        unsafe {
            println!("static value in new thread: {}", VAR);
            VAR = VAR + 1;
        }
    });

    // 等待新线程先运行
    new_thread.join().unwrap();
    unsafe {
        println!("static value in main thread: {}", VAR);
    }
}

///堆,如何在多个线程间访问Box创建的变量
// Arc 原子引用计数,可以安全的用于并发环境的类型,字母 “a” 代表
// 原子性（atomic），所以这是一个原子引用计数（atomically reference counted）类型。原
// 子性是另一类这里还未涉及到的并发原语：请查看标准库中 std: :atomic 的文档来获取更多细
// 节。其中的要点就是：原子性类型工作起来类似原始类型，不过可以安全的在线程间共享
pub fn share_mem_demo2(){
    //Arc正是利用Box来实现的一个通过引用计数来共享状态的包裹类
    let var : Arc<i32> = Arc::new(5);
    let share_var = var.clone();
    // 创建一个新线程
    let new_thread = thread::spawn(move|| {
        println!("share value in new thread: {}, address: {:p}", share_var, &*share_var);
    });

    // 等待新建线程先执行
    new_thread.join().unwrap();
    println!("share value in main thread: {}, address: {:p}", var, &*var);
}
//通过上面的运行结果，我们也可以发现新建线程和主线程中打印的address是一样的，说明状态确实是在同一个内存地址处。
//如果Box在堆上分配的资源仅在一个线程中使用，那么释放时，就非常简单，使用完，及时释放即可。如果是要在多个线程中使用，就需要面临两个关键问题：
//
// 资源何时释放？
// 线程如何安全的并发修改和读取？
// 由于上面两个问题的存在，这就是为什么我们不能直接用Box变量在线程中共享的原因，可以看出来，共享内存比消息传递机制似乎要复杂许多。
// Rust用了引用计数的方式来解决第一个问题，在标准库中提供了两个包裹类，除了上面一个用于多线程的std::sync::Arc之外，
// 还有一个不能用于多线程的std::rc::Rc。在使用时，可以根据需要进行选择。如果你一不小心把std::rc::Rc用于多线程中，编译器会毫不客气地纠正你的。


/// 等待-通知
//通知必然是因为有等待，所以通知和等待几乎都是成对出现的，比如std::sync::Condvar::wait和std::sync::Condvar::notify_one，std::sync::Condvar::notify_all。
//等待所使用的对象，与通知使用的对象是同一个对象，从而该对象需要在多个线程之间共享，参见下面的例子。
// 除了Condvar之外，其实锁也是具有自动通知功能的，当持有锁的线程释放锁的时候，等待锁的线程就会自动被唤醒，以抢占锁。关于锁的介绍，在下面有详解。
// 通过条件变量和锁，还可以构建更加复杂的自动通知方式，比如std::sync::Barrier。
// 通知也可以是1:1的，也可以是1:N的，Condvar可以控制通知一个还是N个，而锁则不能控制，只要释放锁，所有等待锁的其他线程都会同时醒来，而不是只有最先等待的线程。

//这个例子展示了如何通过条件变量和锁来控制新建线程和主线程的同步，让主线程等待新建线程执行后，才能继续执行

//放到main函数中测试
pub fn wait_notify_demo1(){
    //Mutex是Rust中的一种锁。Condvar（条件变量）需要和Mutex一同使用，因为有Mutex保护，Condvar并发才是安全的
    let pair = Arc::new(
        ( Mutex::new(false), Condvar::new() ));
    let pair2 = pair.clone();

    // 创建一个新线程
    thread::spawn(move|| {
        let (ref lock, ref cvar) = *pair2;
        //Mutex::lock方法返回的是一个MutexGuard，在离开作用域的时候，自动销毁，从而自动释放锁，从而避免锁没有释放的问题。
        let mut started = lock.lock().unwrap();
        println!("子线程加锁成功");
        *started = true;
        cvar.notify_one();
        println!("notify main thread");
    });
    
    let (ref lock, ref cvar) = *pair;
    let mut started = lock.lock().unwrap();
    println!("主线程加锁成功");
    while !*started {
        println!("before wait");
        //Condvar在等待时，会释放锁,被通知唤醒时，会重新获得锁，从而保证并发安全
        started = cvar.wait(started).unwrap();
        println!("after wait");
    }
}

///使用互斥量，多线程计数
#[test]
pub fn mutex_demo(){
    //因为在多个线程中访问，使用Arc包装
    let counter = Arc::new(Mutex::new(0));
    //保存线程句柄
    let mut handles= vec![];

    //十个线程
    for _ in 0..10 {
        let counter = Arc::clone(&counter);//克隆，引用数+1
        let handle = thread::spawn(move || {
            // lock 调用 返回 一个叫做
            // MutexGuard 的智能指针。这个智能指针实现了 Deref 来指向其内部数据；其也提供了一个
            // Drop 实现当 MutexGuard 离开作用域时自动释放锁
            let mut num  = counter.lock().unwrap();
            *num  += 1;
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("count = {}", counter.lock().unwrap())

}